﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using SharpRomans.Support;

namespace SharpRomans
{
	public class RomanFigure : IConvertible, IComparable<RomanFigure>, IComparable<short>, IEquatable<RomanFigure>, IEquatable<short>, IEquatable<char>, IValuable
	{
		public char Figure { get; private set; }
		public short Value { get; private set; }
		public string Description { get; private set; }

		public bool IsSubstractive { get; private set; }
		public bool IsSingle { get; private set; }
		public bool IsRepeteable { get; private set; }

		private readonly string _string;
		private RomanFigure(char figure, short value, string description, bool isSubstractive, bool isSingle, bool isRepeteable)
		{
			Figure = figure;
			_string = figure.ToString(CultureInfo.InvariantCulture);
			Value = value;
			Description = description;
			IsSubstractive = isSubstractive;
			IsSingle = isSingle;
			IsRepeteable = isRepeteable;
		}

		public static readonly RomanFigure N = new RomanFigure('N', 0, "nulla", false, false, false);
		public static readonly RomanFigure I = new RomanFigure('I', 1, "unus", true, false, true);
		public static readonly RomanFigure V = new RomanFigure('V', 5, "quique", false, true, false);
		public static readonly RomanFigure X = new RomanFigure('X', 10, "decem", true, false, true);
		public static readonly RomanFigure L = new RomanFigure('L', 50, "quinquaginta", false, true, false);
		public static readonly RomanFigure C = new RomanFigure('C', 100, "centum", true, false, true);
		public static readonly RomanFigure D = new RomanFigure('D', 500, "quingenti", false, true, false);
		public static readonly RomanFigure M = new RomanFigure('M', 1000, "mille", false, false, true);

		public static readonly IEnumerable<RomanFigure> All = new[] { N, I, V, X, L, C, D, M };

		#region equality

		public bool Equals(RomanFigure figure)
		{
			if (ReferenceEquals(null, figure)) return false;
			if (ReferenceEquals(this, figure)) return true;
			return figure.Value == Value;
		}

		public bool Equals(char figure)
		{
			return figure == Figure;
		}

		public bool Equals(short figure)
		{
			return figure == Value;
		}

		public override bool Equals(object obj)
		{
			if (ReferenceEquals(null, obj)) return false;
			if (ReferenceEquals(this, obj)) return true;
			
			if (obj is char) return Equals((char)obj);
			if (obj is short) return Equals((short)obj);
			if (obj is RomanFigure) return Equals((RomanFigure)obj);

			return false;
		}

		public override int GetHashCode()
		{
			return Value.GetHashCode();
		}

		public static bool operator ==(RomanFigure left, RomanFigure right)
		{
			return EqualityComparer<RomanFigure>.Default.Equals(left, right);
		}

		public static bool operator !=(RomanFigure left, RomanFigure right)
		{
			return !EqualityComparer<RomanFigure>.Default.Equals(left, right);
		}

		public static bool operator ==(RomanFigure left, short right)
		{
			if (left == null) return false;
			return left.Equals(right);
		}

		public static bool operator !=(RomanFigure left, short right)
		{
			if (left == null) return true;
			return !left.Equals(right);
		}
		
		public static bool operator ==(short left, RomanFigure right)
		{
			if (right == null) return false;
			return right.Equals(left);
		}
		
		public static bool operator !=(short left, RomanFigure right)
		{
			if (right == null) return true;
			return !right.Equals(left);
		}
		
		public static bool operator ==(RomanFigure left, char right)
		{
			if (left == null) return false;
			return left.Equals(right);
		}

		public static bool operator !=(RomanFigure left, char right)
		{
			if (left == null) return true;
			return !left.Equals(right);
		}
		
		public static bool operator ==(char left, RomanFigure right)
		{
			if (right == null) return false;
			return right.Equals(left);
		}

		public static bool operator !=(char left, RomanFigure right)
		{
			if (right == null) return true;
			return !right.Equals(left);
		}

		#endregion

		public override string ToString()
		{
			return _string;
		}

		#region IConvertible

		TypeCode IConvertible.GetTypeCode()
		{
			return TypeCode.Object;
		}

		bool IConvertible.ToBoolean(IFormatProvider provider)
		{
			return Convert.ToBoolean(Value);
		}

		char IConvertible.ToChar(IFormatProvider provider)
		{
			return Figure;
		}

		sbyte IConvertible.ToSByte(IFormatProvider provider)
		{
			return Convert.ToSByte(Value);
		}

		byte IConvertible.ToByte(IFormatProvider provider)
		{
			return Convert.ToByte(Value);
		}

		short IConvertible.ToInt16(IFormatProvider provider)
		{
			return Value;
		}

		ushort IConvertible.ToUInt16(IFormatProvider provider)
		{
			return Convert.ToUInt16(Value);
		}

		int IConvertible.ToInt32(IFormatProvider provider)
		{
			return Value;
		}

		uint IConvertible.ToUInt32(IFormatProvider provider)
		{
			return Convert.ToUInt32(Value);
		}

		long IConvertible.ToInt64(IFormatProvider provider)
		{
			return Value;
		}

		ulong IConvertible.ToUInt64(IFormatProvider provider)
		{
			return Convert.ToUInt64(Value);
		}

		float IConvertible.ToSingle(IFormatProvider provider)
		{
			return Value;
		}

		double IConvertible.ToDouble(IFormatProvider provider)
		{
			return Value;
		}

		decimal IConvertible.ToDecimal(IFormatProvider provider)
		{
			return Value;
		}

		DateTime IConvertible.ToDateTime(IFormatProvider provider)
		{
			throw invalidCast(typeof(DateTime));
		}

		private InvalidCastException invalidCast(Type other)
		{
			return new InvalidCastException(string.Format("Invalid cast from '{0}' to '{1}'", typeof(RomanFigure).Name, other.Name));
		}

		public string ToString(IFormatProvider provider)
		{
			return _string.ToString(provider);
		}

		object IConvertible.ToType(Type conversionType, IFormatProvider provider)
		{
			// only called when another CLR type is passed. So unless it is itself (weird) it cannot be converted
			if (conversionType == typeof(RomanFigure)) return this;
			throw invalidCast(conversionType);
		}

		#endregion

		#region Parse

		public static RomanFigure Parse(string figure)
		{
			return Parse(char.Parse(figure));
		}

		public static RomanFigure Parse(char figure)
		{
			RomanFigure parsed;
			if (!TryParse(figure, out parsed))
			{
				throw new ArgumentException(string.Format("Requested value '{0}' was not found", figure), "figure");
			}
			return parsed;
		}

		public static bool TryParse(char figure, out RomanFigure parsed)
		{
			parsed = All.FirstOrDefault(f => f.Figure.Equals(figure));
			return parsed != null;
		}

		public static RomanFigure Parse(short value)
		{
			try
			{
				return All.Single(f => f.Value.Equals(value));
			}
			catch (InvalidOperationException ex)
			{
				throw new ArgumentException(string.Format("Requested value '{0}' was not found", value), "value", ex);
			}
		}

		#endregion

		internal Tuple<RomanFigure[], short> AsCalculationRow()
		{
			return new Tuple<RomanFigure[], short>(new[] { this }, Value);
		}

		#region comparisons

		public int CompareTo(RomanFigure anotherFigure)
		{
			if (anotherFigure == null) return 1;
			return Value.CompareTo(anotherFigure.Value);
		}

		public int CompareTo(short other)
		{
			return Value.CompareTo(other);
		}

		#region operators

		public static bool operator >(RomanFigure left, RomanFigure right)
		{
			return left != null && left.CompareTo(right) > 0;
		}

		public static bool operator >=(RomanFigure left, RomanFigure right)
		{
			return left == null ? right == null : left.CompareTo(right) >= 0;
		}

		public static bool operator <(RomanFigure left, RomanFigure right)
		{
			return left == null ? right != null : left.CompareTo(right) < 0;
		}

		public static bool operator <=(RomanFigure left, RomanFigure right)
		{
			return left == null || left.CompareTo(right) <= 0;
		}

		public static bool operator >(RomanFigure left, short right)
		{
			return left != null && left.CompareTo(right) > 0;
		}

		public static bool operator >=(RomanFigure left, short right)
		{
			return left != null && left.CompareTo(right) >= 0;
		}

		public static bool operator <(RomanFigure left, short right)
		{
			return left == null || left.CompareTo(right) < 0;
		}

		public static bool operator <=(RomanFigure left, short right)
		{
			return left == null || left.CompareTo(right) <= 0;
		}

		public static bool operator >(short left, RomanFigure right)
		{
			return right == null || right.CompareTo(left) < 0;
		}

		public static bool operator >=(short left, RomanFigure right)
		{
			return right == null || right.CompareTo(left) <= 0;
		}

		public static bool operator <(short left, RomanFigure right)
		{
			return right != null && right.CompareTo(left) > 0;
		}

		public static bool operator <=(short left, RomanFigure right)
		{
			return right != null && right.CompareTo(left) >= 0;
		}

		#endregion

		#endregion

		public static explicit  operator char(RomanFigure figure)
		{
			if (figure == null) throw new ArgumentNullException("figure");

			return figure.Figure;
		}

		public static explicit operator short(RomanFigure figure)
		{
			if (figure == null) throw new ArgumentNullException("figure");

			return figure.Value;
		}

		public static explicit operator RomanFigure(char character)
		{
			return Parse(character);
		}

		public static explicit operator RomanFigure(short number)
		{
			return Parse(number);
		}
	}
}
